package com.cy.pj.sys.service.aspect;


import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * 在spring AOP应用中，基于@Aspect注解描述的类型为一个切面类型
 * 此类当中要封装切入点及通知方法的定义
 * 1)切入点：要切入扩展业务逻辑的一些目标方法集合(例如使用了特点注解描述的方法)
 * 2)通知：封装了扩展业务逻辑的一个方法(spring中也可以使用特定注解进行描述)
 */
@Order
@Aspect
@Component
public class SysLogAspect {
    private static final Logger log = LoggerFactory.getLogger(SysLogAspect.class);

    /**
     * 基于@PiontCut定义切入点,这里的@annotation为一种切点表达式，
     * 表示由RequiredLog注解描述的方法为一个切入点方法，我们要在这样的方法上添加扩展业务逻辑
     */
    @Pointcut("@annotation(com.cy.pj.common.annotation.RequiredLog)")
    public void doLog(){}//此方法只负责承载切入点的定义

    /**
     * 定义一个通知方法，这里使用@Around进行描述，表示在此方法内部可以调用
     * 目标方法，可以在目标方法执行之前或之后做一些拓展业务
     * @param jp 连接点，用于封装要执行的目标方法信息，ProceedingJoinPoint此类型参数只能定义在@Around注解描述中
     * @return 这里的返回值一般为目标切入点方法的执行结果
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable{
        long t1=System.currentTimeMillis();
        log.info("Start:{}",t1);
        try {
            Object result = jp.proceed(); //调用目标方法
            long t2=System.currentTimeMillis();
            log.info("After:{}",t2);
            doSaveLogInfo(jp,t2-t1,null);
            return result;//目标业务方法的执行结果
        }catch(Throwable e){
            e.printStackTrace();
            long t3=System.currentTimeMillis();
            doSaveLogInfo(jp,t3-t1,e);
            throw e;
        }
    }

    @Autowired
    private SysLogService sysLogService;
    //记录用户行为日志
    private void doSaveLogInfo(ProceedingJoinPoint jp, long time,Throwable e) throws Exception {
        //1.获取用户行为日志
        //1.1 获取登录用户名(没做登录时，可以先给个固定值)
        String username = "cgb";
        //1.2 获取 ip 地址
        String ip = "202.106.0.20";
        //1.3 获取操作名(operation)-@RequiredLog 注解中 value 属性的值
        //1.3.1 获取目标对象类型
        Class<?> targetCls = jp.getTarget().getClass();
        //1.3.2 获取目标方法
        MethodSignature ms = (MethodSignature) (jp.getSignature());//getSignature 获取签名(封装了jp里的一些数据)
        //Method targetMethod = ms.getMethod();//用cglib获取方法
        Method targetMethod = targetCls.getDeclaredMethod(ms.getName(),ms.getParameterTypes());//既能用cglib获取方法也能用jdk
        //1.3.3 获取方法上 RequiredLog 注解
        RequiredLog requiredLog = targetMethod.getAnnotation(RequiredLog.class);
        //1.3.4 获取注解中定义操作名
        String operation = requiredLog.operation();
        //1.4 获取方法声明(类全名+方法名)
        String classMethodName = targetCls.getName()+"."+targetMethod.getName();
        //1.5 获取方法实际参数信息
        Object[] args = jp.getArgs();
        String params = new ObjectMapper().writeValueAsString(args);//将数组转换为字符串
        //2.封装用户行为日志
        SysLog sysLog=new SysLog();
        sysLog.setUsername(username);
        sysLog.setIp(ip);
        sysLog.setCreatedTime(new Date());
        sysLog.setOperation(operation);
        sysLog.setMethod(classMethodName);
        sysLog.setParams(params);
        sysLog.setTime(time);
        if(e!=null) {
            sysLog.setStatus(0);
            sysLog.setError(e.getMessage());
        }
        //3.将日志写入数据库
        String userLog=new ObjectMapper().writeValueAsString(sysLog);
        log.info("user.oper {}",userLog);
        sysLogService.saveLog(sysLog);
        //new Thread(() -> sysLogService.saveLog(sysLog)).start();//并发量大的时候线程不够处理不了
    }
}
